// Alap Szerver fork-al

#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <string.h>
//más nem kell, itt fordul ennyivel is.
//kíváncsi vagyok másnál?

#define kliensszamMAX 2 // csoportonként a kliensek max száma
#define listenSocketPort "2222" //ezen a porton figyel a szerver

#define handle_error(msg) do { printf("\n"); perror(msg); exit(EXIT_FAILURE); } while (0)

struct kliens {
	int cfd; //a kliens socket azonosítója
	char ip[INET6_ADDRSTRLEN]; //a kliens IP, tároljuk, de igazából localhoston ez sosem változik
	char port[6]; //a kliens innen kapcsolódott
	int kliensID; //ezzel azonosítjuk a klienst
	int groupID; //ezzel pedig a forkolás csoportjait
	int PID; // eltároljuk a láncolt listában a PID et is
	struct kliens* next;
};

char listenip[INET6_ADDRSTRLEN];
struct kliens* kliensek=NULL;

struct addrinfo kriterium;
struct addrinfo *resultAddressInfo;

char socketInputBuffer[100]; //a kliensektől jövő adat ide kerül

int listenSocket;
int fdmax;
int i;
int kliensszam=0; // a csoportban aktuálisan bent lévő kliensek száma
int group=0; // a forkok által létrehozott csoportok száma

fd_set master;
fd_set readfd;

void serverListen(){
	printf("\nSzerver elindult");
	printf("\nServer PID: %d",getpid()); //kírjuk a PID-et, ehhez kapcsolódnak, aztán a forkkal child processbe kerülnek
	fflush(stdout); // ez kell, mert különben lehet késve ír ki.

	memset(&kriterium,0,sizeof(kriterium));
	kriterium.ai_family= AF_UNSPEC;
	kriterium.ai_socktype=SOCK_STREAM;
	kriterium.ai_flags=AI_PASSIVE;
	getaddrinfo(NULL,listenSocketPort,&kriterium, &resultAddressInfo);

	//a figyelő port ide
	int p = resultAddressInfo->ai_family==AF_INET ?
				ntohs(((struct sockaddr_in*)resultAddressInfo->ai_addr)->sin_port):
				ntohs(((struct sockaddr_in6*)resultAddressInfo->ai_addr)->sin6_port);
	//és IP begyűjtése
	inet_ntop(
			resultAddressInfo->ai_family,
			resultAddressInfo->ai_family==AF_INET ?
					&(((struct sockaddr_in*)resultAddressInfo->ai_addr)->sin_addr):
					&(((struct sockaddr_in6*)resultAddressInfo->ai_addr)->sin6_addr),
			listenip, //IP ide
			resultAddressInfo->ai_family==AF_INET ?
					INET_ADDRSTRLEN:INET6_ADDRSTRLEN
	);

	printf("\nItt akarunk figyelni: %s:%d",listenip,p);
	fflush(stdout);

	//akkor elkezdjük felépíteni a listen-t
	listenSocket=socket(resultAddressInfo->ai_family, resultAddressInfo->ai_socktype, resultAddressInfo->ai_protocol);

	int optionsValue = 1; // a hibamentes listen socket kezeléshez
	setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &optionsValue, sizeof optionsValue);

	if (listenSocket==-1)
		handle_error("socket");

	if(bind(listenSocket,resultAddressInfo->ai_addr,resultAddressInfo->ai_addrlen))
		handle_error("bind");

	if(listen(listenSocket,kliensszamMAX))
		handle_error("listen");

	//oké nincs hiba
	fdmax=listenSocket;
	FD_ZERO(&master);
	FD_ZERO(&readfd);
	FD_SET(listenSocket,&master);
	printf("\nFigyelunk is...");
	fflush(stdout);
}

void msgtoKliens(int k,char* c,int i){
	send(k,c,strlen(c),0);
	char c1[10];
	sprintf(c1,"%d\n",i);
	send(k,c1,strlen(c1),0);
}

void klienshozzaad(struct sockaddr kliensaddr, int new_fd){
	FD_SET(new_fd,&master);
	if (new_fd>fdmax)
		fdmax=new_fd;

	//új kliens legyártása
	struct kliens* ujKliens=(struct kliens*)malloc(sizeof(struct kliens));

	// a kliens IP és port adatainak előkerítése
	inet_ntop(
			kliensaddr.sa_family,
			kliensaddr.sa_family == AF_INET ?
					&(((struct sockaddr_in*) &kliensaddr)->sin_addr) :
					&(((struct sockaddr_in6*) &kliensaddr)->sin6_addr),
			ujKliens->ip,
			resultAddressInfo->ai_family == AF_INET ?
					INET_ADDRSTRLEN : INET6_ADDRSTRLEN
					);
	int p = kliensaddr.sa_family==AF_INET ?
			ntohs(((struct sockaddr_in*)&kliensaddr)->sin_port):
			ntohs(((struct sockaddr_in6*)&kliensaddr)->sin6_port);
	sprintf(ujKliens->port,"%d",p);

	//a kliens adatok feltöltése
	ujKliens->cfd=new_fd;
	ujKliens->PID=getpid();
	ujKliens->next=kliensek;
	ujKliens->kliensID=kliensszam;
	kliensszam++;
	ujKliens->groupID=group;
	kliensek=ujKliens;
	msgtoKliens(ujKliens->cfd,"\ngroupID: ",ujKliens->groupID);
	msgtoKliens(ujKliens->cfd,"\nkliensID: ",ujKliens->kliensID);
	msgtoKliens(ujKliens->cfd,"\nserverPID: ",ujKliens->PID);
	printf("\nkliens kapcsolodott: %s:%s --- %d/%d",ujKliens->ip,ujKliens->port,ujKliens->groupID,ujKliens->kliensID);
	fflush(stdout);
}

void klienstorles(int i){
	FD_CLR(i, &master);
	if(kliensek->cfd==i){
		struct kliens* torlendo=kliensek;
		kliensek=torlendo->next;
		printf("\ntorlendo: %d",torlendo->kliensID);
		fflush(stdout);
		free(torlendo);
	}
	else{
		struct kliens* it=kliensek->next;
		struct kliens* prev=kliensek;
		while(it->cfd!=i){
			prev=it;
			it=it->next;
		}
		printf("\ntorlendo: %d",it->kliensID);
		fflush(stdout);
		prev->next=it->next;
		free(it);
	}
	int fdmaxuj=0;
	int j=0;
	for(j=0;j<fdmax;j++){
		if(FD_ISSET(j,&master)){
			fdmaxuj=j;
		}
	}
	fdmax=fdmaxuj;
	close(i);
}

void processLoop(){

	struct sockaddr kliensaddr; //a kliens socket adatai ebbben lesznek

	while(1){
		readfd=master;
		select(fdmax+1,&readfd,NULL,NULL,NULL); //beesik egy kliens

		for(i=0;i<=fdmax;i++){ //itt minden azonos az óraival
			if(FD_ISSET(i,&readfd)){
				if(i==listenSocket){
					socklen_t kliensaddrmeret=sizeof(kliensaddr);
					int new_fd=accept(listenSocket,&kliensaddr,&kliensaddrmeret); //itt akceptáljuk a klienst
					if (new_fd==-1)
						handle_error("accept"); // ha zűr volt leállunk

					if(kliensszam<kliensszamMAX){ // és itt kissé átszabtam a programot, sztem érthetőbb
						klienshozzaad(kliensaddr,new_fd);// oké, még fork on belül vagyunk a klienst simán addoljuk
					} //a kliensszam leptetodik ugye
					//es itt viszont mar vizsgaljuk mi is van a kliensszammal?
					if(kliensszam==kliensszamMAX){ //elertuk a limitet: fork
						pid_t pid = fork();
						if (pid==0){ // child vagyok
							printf("\n%d csoport server PID-je mostantol: %d",group, getpid()); // ki is írjuk az új child PIDet
							fflush(stdout);
							FD_CLR(listenSocket,&master); //a listen socket nem kell
							close(listenSocket);
							struct kliens* kl=kliensek; // a klienseknek szépen elküldjük, hogy őket már más server szál szolgálja ki
							while(kl!=NULL){
								kl->PID=getpid();
								msgtoKliens(kl->cfd,"\nserverPID: ",kl->PID);
								kl=kl->next;
							}
						}
						else{ // parent vagyok, fork volt, a dolgaimat tehát ki kell nullázni és újra gyártani
							FD_ZERO(&master);
							FD_ZERO(&readfd);
							FD_SET(listenSocket,&master);
							kliensszam=0;
							struct kliens* kl; // a klienseket is
							while(kliensek!=NULL){
								kl=kliensek;
								kliensek=kliensek->next;
								close(kl->cfd);
								free(kl);
							}
						}
						group++; // na még megléptetjük a csoport azonosítót és készen is vagyunk új kliens felvételre
					}
				}
				else{
					int fogadottByteok=recv(i,socketInputBuffer,sizeof(socketInputBuffer),0);
					if(fogadottByteok<=0){
						klienstorles(i);
					}
					else{ //klienstől fogadott adat feldolgozása

					}
				}
			}
		}

	}

}

int main(){

	serverListen();

	processLoop();

	return 0;
}
